//
//  PSNode.m
//  PrefCore
//
//  Created by Sergey on 5/12/14.
//  Copyright (c) 2014 Play-on-smart. All rights reserved.
//

#import "PSNode.h"
#import "PSMove.h"
#import "PSNodeScore.h"

@interface PSNode () {

    PSHandHash _hands[PS_GAME_PLAYERS];

}

@end

@implementation PSNode

-(instancetype)init {
    if (self = [super init]) {
        _children = [[PSLinkedList alloc] init];
        _bucketIndex = PS_NO_HASH_BUCKET;
    }
    return self;
}

+(PSObjectPool*)pool {
    static PSObjectPool *pool = nil;
    __weak Class weakClass = self;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        pool = [[PSObjectPool alloc] initWithCreateObject:^{
            return [[weakClass alloc] init];
        }];
    });
    return pool;
}

+(PSHashSet*)set {
    static PSHashSet *set = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        set = [[PSHashSet alloc] init];
    });
    return set;
}

-(PSHandHash*)hands {
    return _hands;
}

+(PSNode*)keyForPlayer:(PSPlayerId)player andHands:(PSHandHash*)hands {
    static PSNode* search;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        search = [[PSNode alloc] init];
    });
    search.player = player;
    memcpy(search.hands, hands, PS_GAME_PLAYERS*sizeof(PSHandHash));
    return search;
}

+(instancetype)nodeWithLevel:(PSTreeLevel*)level
                     starter:(PSPlayerId)starter
                       hand0:(PSHandHash)hand0
                       hand1:(PSHandHash)hand1
                       hand2:(PSHandHash)hand2 {
    PSNode *n = [[PSNode pool] get];
    n.propagated = 0;
    // root has no parent
    n.parents = 0;
    // first player
    n.player = starter;
    // populate hands
    n.hands[0] = hand0;
    n.hands[1] = hand1;
    n.hands[2] = hand2;
    // initialize level 0
    [level.pending addLast:n];
    return n;
}

+(instancetype)nodeWithLevel:(PSTreeLevel*)level
                      parent:(PSNode*)parent
                       hands:(PSHandHash*)hands
                      player:(PSPlayerId)player
                      winner:(PSPlayerId)winner
                       card0:(PSCard*)c0
                       card1:(PSCard*)c1
                       card2:(PSCard*)c2 {
    PSNode *n = [[PSNode pool] get];
    n.propagated = 0;
    n.winner = winner;
    n.parents = 1;
    n.player = player;
    // set hands
    memcpy(n.hands, hands, PS_GAME_PLAYERS*sizeof(PSHandHash));
    // add this position and a move leading to it to the parent's child list
    [parent.children addLast:[PSMove moveWithCard0:c0 card1:c1 card2:c2 andNode:n]];
    // add this node to the level' list of newly created positions
    [level.pending addLast:n];
    return n;
}

-(void)destroy {
    NSAssert(_parents <= 1, @"parents > 1");
    // Traverse children, and decrease the number of references from parents.
    // We do not actually clear children here, see the set: method.
    [_children start];
    while ([_children hasNextEntry]) {
        PSMove *move = [_children nextEntry];
        // destroy move object
        [move destroyWithRecycle:NO];
    }
    [[PSMove pool] putList:_children];
    _propagated = 0;
    // remove from leafs map
    if (_bucketIndex != PS_NO_HASH_BUCKET) {
        [[PSNode set] remove:self];
    }
    // Now put this node back to the pool
    [[PSNode pool] putObject:self];
}

-(NSString*)description {
    return [NSString
        stringWithFormat:@"pa: %u sc: %@ w: %u st: %u ch: %lu h0: %@ h1: %@ h2: %@"
    ,   _parents
    ,   PSScoreDescription(_propagated)
    ,   _winner
    ,   _player
    ,   (unsigned long)_children.count
    ,   PSHandDescription(_hands[0])
    ,   PSHandDescription(_hands[1])
    ,   PSHandDescription(_hands[2])
    ];
}

-(NSUInteger)hash {
    NSUInteger result = 31 + _player;
    result = 31 * result + _hands[0];
    result = 31 * result + _hands[1];
    result = 31 * result + _hands[2];
    return result;
}

-(BOOL)isEqual:(id)object {
    PSNode *n = (PSNode*)object;
    PSHandHash *h = n.hands;
    return _player == n.player
      && _hands[0] == h[0]
      && _hands[1] == h[1]
      && _hands[2] == h[2];
}

@end
